package com.morningstar.qs.api.basic.common.shell;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.apache.log4j.Logger;

public class ShellManager {
	private static Logger gLogger = Logger.getLogger(ShellManager.class);

	public static final int DEFAULT_TIMEOUT = 10 * 1000;
	public static final int DEFAULT_INTERVAL = 1000;

	/**
	 * Executes the specified command in a separate process. The method then
	 * blocks until the process returned. If an error arises during the
	 * execution or if the executed process returned an non-null return code,
	 * the content of the process' stderr is returned to the caller. If the
	 * execution is fine, null is returned.
	 * 
	 * @param command
	 *            String
	 * @return CommandResult
	 */
	public static ShellResult exec(String command) {
		long start = System.currentTimeMillis();
		long last;

		ShellResult shResult = new ShellResult();
		try {
			Process process = Runtime.getRuntime().exec(command);
			process(process, shResult);
			if (process != null) {
				process.destroy();
			}
			last = (System.currentTimeMillis() - start) / 1000;
			gLogger.info("Execute command [" + command + "], last [" + last + "] s.");
		} catch (Exception e) {
			last = (System.currentTimeMillis() - start) / 1000;
			String error = "Execute command [" + command + "] last [" + last + "] s, failed [" + e.getMessage() + "]";
			gLogger.error(error, e);
			shResult.setExitValue(ShellResult.EXIT_VALUE_UNEXCEPTED);
			shResult.setErrorOutput(error);
		}
		return shResult;
	}
	
	public static ShellResult exec(String[] sh) {
		long start = System.currentTimeMillis();
		long last;

		ShellResult shResult = new ShellResult();
		try {
			Process process = Runtime.getRuntime().exec(sh);
			process(process, shResult);
			if (process != null) {
				process.destroy();
			}
			last = (System.currentTimeMillis() - start) / 1000;
			gLogger.info("Execute command [" + sh + "], last [" + last + "] s.");
		} catch (Exception e) {
			last = (System.currentTimeMillis() - start) / 1000;
			String error = "Execute command [" + sh + "] last [" + last + "] s, failed [" + e.getMessage() + "]";
			gLogger.error(error, e);
			shResult.setExitValue(ShellResult.EXIT_VALUE_UNEXCEPTED);
			shResult.setErrorOutput(error);
		}
		return shResult;
	}

	private static void process(Process process, ShellResult shResult) {
		BufferedReader errorReader = null;
		BufferedReader inputReader = null;

		try {
			errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
			inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));

			// timeout control
			long start = System.currentTimeMillis();
			boolean processFinished = false;
			while (System.currentTimeMillis() - start < DEFAULT_TIMEOUT && !processFinished) {
				processFinished = true;
				try {
					process.exitValue();
				} catch (IllegalThreadStateException e) {
					// process hasn't finished yet
					processFinished = false;
					try {
						Thread.sleep(DEFAULT_INTERVAL);
					} catch (InterruptedException e1) {
						gLogger.error("Process, failed [" + e.getMessage() + "]", e);
					}
				}
			}

			if (!processFinished) {
				shResult.setExitValue(ShellResult.EXIT_VALUE_TIMEOUT);
				shResult.setErrorOutput("Command process timeout");
				return;
			}

			shResult.setExitValue(process.waitFor());
			StringBuffer sb;
			String line;

			// parse error info
			if (errorReader.ready()) {
				sb = new StringBuffer();
				while ((line = errorReader.readLine()) != null) {
					sb.append(line);
				}
				shResult.setErrorOutput(sb.toString());
			}

			// parse info
			if (inputReader.ready()) {
				sb = new StringBuffer();
				while ((line = inputReader.readLine()) != null) {
					sb.append(line);
				}
				shResult.setInfoOutput(sb.toString());
			}
		} catch (Exception e) {
			String error = "Command process, failed [" + e.getMessage() + "]";
			gLogger.error(error, e);
			shResult.setExitValue(ShellResult.EXIT_VALUE_UNEXCEPTED);
			shResult.setErrorOutput(error);
		} finally {
			if (errorReader != null) {
				try {
					errorReader.close();
				} catch (IOException e) {
					gLogger.error("Close BufferedReader, failed [" + e.getMessage() + "]", e);
				}
			}
			if (inputReader != null) {
				try {
					inputReader.close();
				} catch (IOException e) {
					gLogger.error("Close BufferedReader, failed [" + e.getMessage() + "]", e);
				}
			}
		}
	}
}

